#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
#include <assert.h>
#include <stdarg.h>
#include <stdlib.h>

typedef struct{
    lua_State*L;
    int       func_ref;   //LUA_REFNIL luaL_ref(L,LUA_REGISTRYINDEX);   lua_rawgeti(L,LUA_REGISTRYINDEX,ref); 
}lua_callbackData;

static void* _new(lua_State*L,int ref){
    lua_callbackData *cd = calloc(1,sizeof(*cd));
    cd->L = L;
    cd->func_ref = ref;
    return cd;
}

static int _gc(lua_State*L){
    if(luaL_checkudata(L,1,"callbackdata")){
        void * d = *(void**)lua_touserdata(L,1);
        printf("___gc\n");
        free(d);
    }
    return 0;
}

static int newmetatable(lua_State*L){
    luaL_newmetatable(L,"callbackdata");
    lua_pushcfunction(L,_gc);
    lua_setfield(L,-2,"__gc");
    return 0;
}

static int callbackdata(lua_State*L){
    lua_callbackData *cd;
    lua_pushvalue(L,1);
    int func_ref = luaL_ref(L,LUA_REGISTRYINDEX);
    *(void**)lua_newuserdata(L,sizeof(void*)) = _new(L,func_ref);
    luaL_setmetatable(L,"callbackdata");
    return 1;
}

static void* call_luafunction(lua_callbackData*l,va_list ap){
    lua_State*L = l->L;
    lua_rawgeti(L,LUA_REGISTRYINDEX,l->func_ref);
    if(lua_isfunction(L,-1)){
        lua_pushlightuserdata(L,ap);
        lua_call(L,1,LUA_MULTRET);
        void* ret = 0;
        if(lua_isinteger(L,-1))
            ret = (void*)((int)lua_tointeger(L,-1));
        else if(lua_islightuserdata(L,-1))
            ret = (void*)lua_touserdata(L,-1);
        else if(lua_isuserdata(L,-1))
            ret = *(void**)lua_touserdata(L,-1);
        else if(lua_isstring(L,-1))
            ret = (void*)lua_tostring(L,-1);
        return ret;
    }
    return 0;
}

static lua_callbackData * getData(va_list ap,int idx){
    void** vp = (void**)(ap);
    return vp[idx];
}

#define DEF_CALLBACK_FUNC(idx) \
static int callfunc##idx(void*first,...){ \
    va_list ap;                          \
    va_start(ap,first);                  \
    ap = ap - sizeof(void*);            \
    lua_callbackData *cd;                \
    cd = getData(ap,(idx)-1);              \
    call_luafunction(cd,ap);             \
    va_end(ap);                          \
}

DEF_CALLBACK_FUNC(1);
DEF_CALLBACK_FUNC(2);
DEF_CALLBACK_FUNC(3);
DEF_CALLBACK_FUNC(4);
DEF_CALLBACK_FUNC(5);
DEF_CALLBACK_FUNC(6);
DEF_CALLBACK_FUNC(7);
DEF_CALLBACK_FUNC(8);

int luaopen_dcallback(lua_State*L){
    newmetatable(L);
    lua_register(L,"callbackdata",callbackdata);
    lua_pushlightuserdata(L,callfunc1); lua_setglobal(L,"callfunc1");
    lua_pushlightuserdata(L,callfunc2); lua_setglobal(L,"callfunc2");
    lua_pushlightuserdata(L,callfunc3); lua_setglobal(L,"callfunc3");
    lua_pushlightuserdata(L,callfunc4); lua_setglobal(L,"callfunc4");
    lua_pushlightuserdata(L,callfunc5); lua_setglobal(L,"callfunc5");
    lua_pushlightuserdata(L,callfunc6); lua_setglobal(L,"callfunc6");
    lua_pushlightuserdata(L,callfunc7); lua_setglobal(L,"callfunc7");
    lua_pushlightuserdata(L,callfunc8); lua_setglobal(L,"callfunc8");
    return 0;
}
